package org.nightscout.lasso;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.TextView;
import com.nightscout.core.dexcom.TrendArrow;
import com.nightscout.core.dexcom.records.CalRecord;
import com.nightscout.core.dexcom.records.EGVRecord;
import com.nightscout.core.dexcom.records.SensorRecord;
import com.nightscout.core.model.GlucoseUnit;
import com.nightscout.core.model.ReceiverStatus;
import com.nightscout.core.preferences.NightscoutPreferences;
import com.nightscout.core.utils.GlucoseReading;
import com.nightscout.core.utils.IsigReading;
import net.tribe7.common.base.Optional;
import org.joda.time.DateTime;
import org.joda.time.Hours;
import org.joda.time.Instant;
import org.joda.time.Minutes;
import org.joda.time.Seconds;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.nightscout.lasso.model.CalibrationDbEntry;
import org.nightscout.lasso.model.SensorDbEntry;
import org.nightscout.lasso.model.SgvDbEntry;
import org.nightscout.lasso.preferences.AndroidPreferences;
import org.nightscout.lasso.settings.SettingsActivity;
import java.text.DecimalFormat;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import butterknife.ButterKnife;
import butterknife.InjectView;
import static com.nightscout.core.dexcom.SpecialValue.getEGVSpecialValue;
import static com.nightscout.core.dexcom.SpecialValue.isSpecialValue;
import static org.joda.time.Duration.standardMinutes;
public class MainActivity extends AppCompatActivity {
@InjectView(R.id.webView)
WebView mWebView;
@InjectView(R.id.sgValue)
TextView mTextSGV;
@InjectView(R.id.syncButton)
ImageButton uploadButton;
@InjectView(R.id.usbButton)
ImageButton receiverButton;
private PillBoxWidget dPill;
private PillBoxWidget rawPill;
private PillBoxWidget receiverBatteryPill;
private PillBoxWidget uploaderBatteryPill;
private PillBoxWidget minago;
private NightscoutPreferences preferences;
private DateTime lastEgv;
private Handler mHandler = new Handler();
public Runnable updateTimeAgo = new Runnable() {
@Override
public void run() {
setTimeAgoPill(lastEgv);
mHandler.removeCallbacks(updateTimeAgo);
long delay = Seconds.seconds(30).toStandardDuration().getMillis();
if (lastEgv != null) {
long delta = Instant.now().getMillis() - lastEgv.getMillis();
delay = delta % standardMinutes(1).getMillis();
Log.w("Delay", "Delay: " + delay);
Log.w("Delay", "Delta: " + delta);
}
mHandler.postDelayed(updateTimeAgo, delay);
}
};
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(NightscoutMonitor.NEW_READING_ACTION)) {
int uploaderBattery = Optional.fromNullable(intent.getExtras().getInt("uploaderBattery")).or(-1);
int receiverBattery = Optional.fromNullable(intent.getExtras().getInt("receiverBattery")).or(-1);
List<EGVRecord> egvRecords = SgvDbEntry.getLastEgvRecords(new DateTime().minus(Hours.hours(4)));
updateView(egvRecords, uploaderBattery, receiverBattery);
} else if (intent.getAction().equals(NightscoutMonitor.RECEIVER_STATE_INTENT)) {
Optional<String> receiverStatus = Optional.fromNullable(intent.getExtras().getString("state"));
if (receiverStatus.isPresent()) {
Log.d("StateReceiver", "Received state: " + receiverStatus.get());
if (receiverStatus.get().equals(ReceiverStatus.RECEIVER_CONNECTED.name())) {
receiverButton.setBackgroundResource(R.drawable.ic_usb);
} else {
receiverButton.setBackgroundResource(R.drawable.ic_nousb);
}
}
} else if (intent.getAction().equals(NightscoutMonitor.MQTT_RESPONSE_STATUS_INTENT)) {
int res = (intent.getExtras().getBoolean(NightscoutMonitor.MQTT_STATUS_EXTRA_FIELD)) ? R.drawable.ic_cloud : R.drawable.ic_nocloud;
uploadButton.setImageResource(res);
}
}
};
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("onCreate", "Created");
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
Intent intent = new Intent(this, NightscoutMonitor.class);
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter(NightscoutMonitor.NEW_READING_ACTION);
intentFilter.addAction(NightscoutMonitor.RECEIVER_STATE_INTENT);
intentFilter.addAction(NightscoutMonitor.MQTT_RESPONSE_STATUS_INTENT);
broadcastManager.registerReceiver(broadcastReceiver, intentFilter);
if (SgvDbEntry.getLastEgv().isPresent()) {
lastEgv = SgvDbEntry.getLastEgv().get().getWallTime();
}
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setDatabaseEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setUseWideViewPort(false);
mWebView.setVerticalScrollBarEnabled(false);
mWebView.setHorizontalScrollBarEnabled(false);
mWebView.setBackgroundColor(0);
mWebView.loadUrl("file:///android_asset/index.html");
// disable scroll on touch
mWebView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return (event.getAction() == MotionEvent.ACTION_MOVE);
}
});
receiverBatteryPill = new PillBoxWidget(R.id.rcbat);
receiverBatteryPill.update("RB", "??");
uploaderBatteryPill = new PillBoxWidget(R.id.ulbat);
uploaderBatteryPill.update("UB", "??");
minago = new PillBoxWidget(R.id.minago);
rawPill = new PillBoxWidget(R.id.rawIsig);
rawPill.update("Raw", "Noise");
preferences = new AndroidPreferences(getApplicationContext());
dPill = new PillBoxWidget(R.id.deltapill);
dPill.update(preferredUnitsString(preferences.getPreferredUnits()), "??");
startService(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
private void setTimeAgoPill(DateTime start) {
if (start == null) {
return;
}
Minutes minutes = Minutes.minutesBetween(start, Instant.now());
PeriodFormatterBuilder periodFormatterBuilder = new PeriodFormatterBuilder();
if (minutes.toStandardWeeks().getWeeks() > 1) {
periodFormatterBuilder.appendWeeks().appendSuffix(getApplicationContext().getString(R.string.week), getApplicationContext().getString(R.string.weeks));
} else if (minutes.toStandardDays().getDays() > 1) {
periodFormatterBuilder.appendDays().appendSuffix(getApplicationContext().getString(R.string.day), getApplicationContext().getString(R.string.days));
} else if (minutes.toStandardHours().getHours() > 1) {
periodFormatterBuilder.appendHours().appendSuffix(getApplicationContext().getString(R.string.hour), getApplicationContext().getString(R.string.hours));
} else {
periodFormatterBuilder.appendMinutes().appendSuffix(getApplicationContext().getString(R.string.minute), getApplicationContext().getString(R.string.minutes));
}
periodFormatterBuilder.appendLiteral(" ago");
PeriodFormatter formatter = periodFormatterBuilder.toFormatter();
String pattern = "(\\d+)(.*)";
Pattern r = Pattern.compile(pattern);
Log.d("minutes", "Minutes: " + formatter.print(minutes));
Matcher m = r.matcher(formatter.print(minutes));
if (m.find()) {
String number = m.group(1);
String ago = m.group(2);
minago.update(ago, number);
}
}
@Override
public void onPause() {
mWebView.pauseTimers();
mWebView.onPause();
mHandler.removeCallbacks(updateTimeAgo);
super.onPause();
}
@Override
protected void onResume() {
Log.d("onResume", "Resumed");
mWebView.onResume();
mWebView.resumeTimers();
// List<EGVRecord> egvRecords = SgvDbEntry.getLastEgvRecords(new DateTime().minus(Hours.hours(4)));
// Log.d("onResume", "Number of records => " + egvRecords.size());
dPill.restoreView();
rawPill.restoreView();
receiverBatteryPill.restoreView();
uploaderBatteryPill.restoreView();
rawPill.restoreView();
mHandler.post(updateTimeAgo);
super.onResume();
}
@Override
protected void onStart() {
Log.d("onStart", "Started");
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
Log.d("onStop", "Stopped");
}
@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
super.onDestroy();
}
private void updateView(List<EGVRecord> egvRecords, int uploaderBattery, int receiverBattery) {
receiverBatteryPill.update(String.valueOf(receiverBattery));
uploaderBatteryPill.update(String.valueOf(uploaderBattery));
Log.d("message receiver", "Battery: " + uploaderBattery);
lastEgv = egvRecords.get(egvRecords.size() - 1).getWallTime();
setTimeAgoPill(lastEgv);
String sgvText = getSGVStringByUnit(egvRecords.get(egvRecords.size() - 1).getReading(), egvRecords.get(egvRecords.size() - 1).getTrend());
mTextSGV.setText(sgvText);
JSONArray array = new JSONArray();
for (EGVRecord record : egvRecords) {
try {
array.put(record.toJSON());
} catch (JSONException e) {
e.printStackTrace();
}
}
Log.e("MainActivity", "Json array " + array);
mWebView.loadUrl("javascript:updateData(" + array + ")");
// TODO - Moved watchmaker integration from this activity to the service to ensure that new updates will be pushed regardless of whether
// or not the activity is running so long as the service is active.
String delta = "None";
if (egvRecords.size() > 2) {
GlucoseReading glucoseDelta = egvRecords.get(egvRecords.size() - 1).getReading().subtract(egvRecords.get(egvRecords.size() - 2).getReading());
DecimalFormat fmt;
if (preferences.getPreferredUnits() == GlucoseUnit.MGDL) {
fmt = new DecimalFormat("+#,##0;-#");
} else {
fmt = new DecimalFormat("+#,##0.0;-#");
}
if (egvRecords.get(egvRecords.size() - 2).getReading().asMgdl() > 38 && egvRecords.get(egvRecords.size() - 2).getReading().asMgdl() > 38) {
delta = fmt.format(glucoseDelta.as(preferences.getPreferredUnits()));
}
}
dPill.update(preferredUnitsString(preferences.getPreferredUnits()), String.valueOf(delta));
Optional<CalRecord> lastCal = CalibrationDbEntry.getLastCal();
Optional<SensorRecord> lastSensor = SensorDbEntry.getLastSensor();
EGVRecord lastEgv = egvRecords.get(egvRecords.size() - 1);
IsigReading isigReading = new IsigReading();
if (lastCal.isPresent() && lastSensor.isPresent() && Math.abs(lastEgv.getSystemTime().getMillis() - lastSensor.get().getSystemTime().getMillis()) < Seconds.seconds(10).toStandardDuration().getMillis()) {
isigReading = new IsigReading(lastSensor.get(), lastCal.get(), egvRecords.get(egvRecords.size() - 1));
Log.e("isig", "iSig reading: " + isigReading.asMgdlStr());
} else {
Log.w("isig", "Problem matching sensor to egv for isig calculation");
Log.w("isig", "Last egv: " + lastEgv.getSystemTime().getMillis());
Log.w("isig", "Last sensor: " + lastSensor.get().getSystemTime().getMillis());
}
rawPill.update(egvRecords.get(egvRecords.size() - 1).getNoiseMode().name(), isigReading.asStr(preferences.getPreferredUnits()));
}
private String preferredUnitsString(GlucoseUnit units) {
return (units == GlucoseUnit.MGDL ? "mg/dL" : "mmoL");
}
private String getSGVStringByUnit(GlucoseReading sgv, TrendArrow trend) {
String sgvStr = sgv.asStr(preferences.getPreferredUnits());
return (sgv.asMgdl() != -1) ?
(isSpecialValue(sgv)) ?
getEGVSpecialValue(sgv).get().toString() : sgvStr + " " + trend.symbol() : "---";
}
public class PillBoxWidget {
private View view;
public PillBoxWidget(int res) {
this.view = findViewById(res);
}
public void update(String header, String value) {
updateHeader(header);
update(value);
}
public void update(String value) {
((TextView) view.findViewById(R.id.pillvalue)).setText(value);
view.setTag(R.id.pillvalue, value);
}
public void updateHeader(String header) {
((TextView) view.findViewById(R.id.heading)).setText(header);
view.setTag(R.id.heading, header);
}
public void restoreView() {
((TextView) view.findViewById(R.id.pillvalue)).setText((String) view.getTag(R.id.pillvalue));
((TextView) view.findViewById(R.id.heading)).setText((String) view.getTag(R.id.heading));
}
public void restoreViewWithConversion() {
Integer val = Integer.valueOf((String) view.getTag(R.id.pillvalue));
GlucoseReading glucoseReading = new GlucoseReading(val, GlucoseUnit.MGDL);
((TextView) view.findViewById(R.id.pillvalue)).setText(glucoseReading.asStr(preferences.getPreferredUnits()));
((TextView) view.findViewById(R.id.heading)).setText((String) view.getTag(R.id.heading));
}
}
}